/*
 * Copyright 2016 - 2019 NXP
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * o Redistributions of source code must retain the above copyright notice, this list
 *   of conditions and the following disclaimer.
 *
 * o Redistributions in binary form must reproduce the above copyright notice, this
 *   list of conditions and the following disclaimer in the documentation and/or
 *   other materials provided with the distribution.
 *
 * o Neither the name of the copyright holder nor the names of its
 *   contributors may be used to endorse or promote products derived from this
 *   software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/*!
 * @file bcc_diagnostics.c
 *
 * Diagnostics part of Battery cell controller SW driver V1.1.
 * Supports boards based on MC33771B and MC33772B.
 *
 * This module is common for all supported models.
 */

/*******************************************************************************
 * Includes
 ******************************************************************************/
#include "../MC3377X_USER.h"
#ifdef MC33771B
#include "bcc_diagnostics.h"

/*******************************************************************************
 * Definitions
 ******************************************************************************/

/*! @brief Time delay t_delay in [us] used in Cell balance fault diagnostics. */
#define BCC_DIAG_T_DELAY          100U

/*! @brief Time to perform auto-zero procedure after enabling the current
 *  channel. */
#define BCC_DIAG_T_AZC_SETTLE     200U

/*! @brief Bit map of odd cells (differs according to number of used cells) */
#define BCC_ODD_CELL_MASK(drvConfig, cid)                                        \
    (((drvConfig->device[(uint8_t)(cid) - 1] == BCC_DEVICE_MC33771) ?            \
            ((drvConfig->cellCnt[(uint8_t)(cid) - 1] % 2) ? 0x02AA5 : 0x1555) :  \
            ((drvConfig->cellCnt[(uint8_t)(cid) - 1] % 2) ? 0x0029 : 0x0015)) &  \
            drvConfig->drvData.cellMap[(uint8_t)(cid) - 1])

/*! @brief Bit map of even cells (differs according to number of used cells) */
#define BCC_EVEN_CELL_MASK(drvConfig, cid)                                       \
    (((drvConfig->device[(uint8_t)(cid) - 1] == BCC_DEVICE_MC33771) ?            \
            ((drvConfig->cellCnt[(uint8_t)(cid) - 1] % 2) ? 0x0155A : 0x2AAA) :  \
            ((drvConfig->cellCnt[(uint8_t)(cid) - 1] % 2) ? 0x0016 : 0x002A)) &  \
            drvConfig->drvData.cellMap[(uint8_t)(cid) - 1])

/*******************************************************************************
 * Prototypes of internal functions
 ******************************************************************************/

/*!
 * @brief This function enters diagnostic mode.
 *
 * @param drvConfig Pointer to driver instance configuration.
 * @param cid Cluster ID of BCC device.
 *
 * @return bcc_status_t Error code.
 */
static bcc_status_t BCC_EnterDiagnostics(bcc_drv_config_t* const drvConfig,
    bcc_cid_t cid);

/*!
 * @brief This function calculates exits diagnostic mode.
 *
 * @param drvConfig Pointer to driver instance configuration.
 * @param devConf Initialization values of BCC registers.
 *
 * @return bcc_status_t Error code.
 */
static bcc_status_t BCC_ExitDiagnostics(bcc_drv_config_t* const drvConfig,
    bcc_cid_t cid);

/*!
 * @brief This function starts on-demand conversion and waits for completion.
 *
 * @param drvConfig Pointer to driver instance configuration.
 * @param cid Cluster ID of BCC device.
 *
 * @return bcc_status_t Error code.
 */
static bcc_status_t BCC_RunCOD(bcc_drv_config_t* const drvConfig,
    bcc_cid_t cid);

/*!
 * @brief This function commands CT or CB diagnostic switches.
 *
 * @param drvConfig Pointer to driver instance configuration.
 * @param cid Cluster ID of BCC device.
 * @param switches Selection of CT or CB switches.
 * @param odd State of odd diagnostic switches.
 * @param even State of even diagnostic switches.
 *
 * @return bcc_status_t Error code.
 */
static bcc_status_t BCC_CommandSwitches(bcc_drv_config_t* const drvConfig,
    bcc_cid_t cid, bcc_diag_switch_sel_t switches,
    bcc_diag_switch_pos_t odd, bcc_diag_switch_pos_t even);

/*!
 * @brief This function computes voltage in uV from register raw values.
 *
 * @param rawMeas Array containing raw values from registers.
 * @param size Size of the array.
 * @param convMeas Resulting array containing converted values.
 */
static inline void BCC_ConvertVoltageArray(const uint16_t rawMeas[],
    uint8_t size, uint32_t convMeas[]);

/*!
 * @brief This function is part overvoltage and undervoltage functional
 * verification. It commands CT switches and measures voltage on cells.
 *
 * @param drvConfig Pointer to driver instance configuration.
 * @param cid Cluster ID of BCC device.
 * @param odd State of odd switches.
 * @param even State of even switches.
 * @param tauDiagN Diagnostic time constant tau_diag,n in [us].
 * @param fltOvrv Result of the function containing CELL_OV_FLT register value.
 * @param fltUndv Result of the function containing CELL_UV_FLT register value.
 *
 * @return bcc_status_t Error code.
 */
static bcc_status_t BCC_DiagOvuvPart(bcc_drv_config_t* const drvConfig,
    bcc_cid_t cid, bcc_diag_switch_pos_t odd, bcc_diag_switch_pos_t even,
    uint32_t tauDiagN, uint16_t *fltOvrv, uint16_t *fltUndv);

/*!
 * @brief This function is part of CTX open detect. It clear OV and UV
 * fault registers, commands CT switches and measures voltage on cells.
 *
 * @param drvConfig Pointer to driver instance configuration.
 * @param cid Cluster ID of BCC device.
 * @param odd State of odd switches.
 * @param even State of even switches.
 * @param tauDiag Diagnostic time constant tau_diag in [us].
 * @param measVal Result of the function containing measured values for
 *                CT 14 - 1 (MC33771) or CT 6 - 1 (MC33772).
 *
 * @return bcc_status_t Error code.
 */
static bcc_status_t BCC_DiagCtxopenPart(bcc_drv_config_t* const drvConfig,
    bcc_cid_t cid, bcc_diag_switch_pos_t odd, bcc_diag_switch_pos_t even,
    uint32_t tauDiag, uint16_t *measVal);

/* Internal function used by DiagCurrentMeas function only. */
/*!
 * @brief This function is part of current measurement diagnostics (the first
 * procedure). It resets Coulomb counter, initiates conversion and reads
 * measured current.
 *
 * @param drvConfig Pointer to driver instance configuration.
 * @param cid Cluster ID of BCC device.
 * @param current Result of the diagnostic containing measured ISENSE
 *                voltage in [uV].
 *
 * @return bcc_status_t Error code.
 */
static bcc_status_t BCC_DiagRunImeas(bcc_drv_config_t* const drvConfig,
    bcc_cid_t cid, int32_t *current);

/* Internal function used by DiagGPIOxOtUt function only. */
/*!
 * @brief This function is part of GPIO OT and UT functional verification.
 *
 * Writes value given in parameter to GPIO_CFG2 register, initiates conversion,
 * reads AN_OT_UT_FLT register and clears OT/UT faults.
 *
 * @param drvConfig Pointer to driver instance configuration.
 * @param cid Cluster ID of BCC device.
 * @param gpioCFG2Val Value of GPIO_CFG2 GPIO configuration register.
 * @param otUtStat Over/Under-temperature status for GPIOs (expected for all
 *                 GPIOs). It contains value of AN_OT_UT_FLT register.
 *
 * @return bcc_status_t Error code.
 */
static bcc_status_t BCC_GpioOtUtPart(bcc_drv_config_t* const drvConfig,
    bcc_cid_t cid, uint16_t gpioCFG2Val, uint16_t *otUtStat);

/*******************************************************************************
 * Internal function
 ******************************************************************************/

/*FUNCTION**********************************************************************
 *
 * Function Name : BCC_EnterDiagnostics
 * Description   : This function enters diagnostic mode.
 *
 *END**************************************************************************/
static bcc_status_t BCC_EnterDiagnostics(bcc_drv_config_t* const drvConfig,
    bcc_cid_t cid)
{
    uint16_t retVal;    /* Value of SYS_CFG1 register. */
    bcc_status_t error = BCC_STATUS_SUCCESS;

    error = BCC_Reg_Update(drvConfig, cid, BCC_REG_SYS_CFG1_ADDR,
                           BCC_W_GO2DIAG_MASK, BCC_DIAG_MODE_ENABLED);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* Check that the device is in diagnostic mode. */
    error = BCC_Reg_Read(drvConfig, cid, BCC_REG_SYS_CFG1_ADDR, 1U, &retVal);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    if (!(retVal & BCC_R_DIAG_ST_MASK))
    {
        return BCC_STATUS_DIAG_FAIL;
    }

    return BCC_STATUS_SUCCESS;
}  

/*FUNCTION**********************************************************************
 *
 * Function Name : BCC_ExitDiagnostics
 * Description   : This function exits diagnostic mode.
 *
 *END**************************************************************************/
static bcc_status_t BCC_ExitDiagnostics(bcc_drv_config_t* const drvConfig,
    bcc_cid_t cid)
{
    return BCC_Reg_Update(drvConfig, cid, BCC_REG_SYS_CFG1_ADDR,
                          BCC_W_GO2DIAG_MASK, BCC_DIAG_MODE_DISABLED);
}

/*FUNCTION**********************************************************************
 *
 * Function Name : BCC_RunCOD
 * Description   : This function starts on-demand conversion and waits for
 *                 completion.
 *
 *END**************************************************************************/
static bcc_status_t BCC_RunCOD(bcc_drv_config_t* const drvConfig, bcc_cid_t cid)
{
    bool complete;      /* Conversion complete flag. */
    bcc_status_t error = BCC_STATUS_SUCCESS;

    error = BCC_Meas_StartConversion(drvConfig, cid);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* Wait for at least 300 us before polling bit EOC_N to avoid any traffic on
     * the communication bus during conversion. */
    BCC_MCU_WaitUs(400);

    do
    {
        error = BCC_Meas_IsConverting(drvConfig, cid, &complete);
        if (error != BCC_STATUS_SUCCESS)
        {
            return error;
        }

    } while (!complete);

    return BCC_STATUS_SUCCESS;
}
  
/*FUNCTION**********************************************************************
 *
 * Function Name : BCC_CommandSwitches
 * Description   : This function commands CT or CB diagnostic switches.
 *
 *END**************************************************************************/
static bcc_status_t BCC_CommandSwitches(bcc_drv_config_t* const drvConfig,
    bcc_cid_t cid, bcc_diag_switch_sel_t switches, bcc_diag_switch_pos_t odd,
    bcc_diag_switch_pos_t even)
{
    uint16_t mask;     /* Register mask. */
    uint16_t command;  /* Command of diagnostic switches (data field of a frame). */

    /* Create content of a frame to command CB outputs */
    if (switches == BCC_SWITCH_SEL_CT)
    {
        mask = BCC_RW_CT_OL_ODD_MASK | BCC_RW_CT_OL_EVEN_MASK;
        command = (even == BCC_SWITCH_POS_OPEN) ? BCC_CT_OL_EVEN_OPEN : BCC_CT_OL_EVEN_CLOSED;
        command |= (odd == BCC_SWITCH_POS_OPEN) ? BCC_CT_OL_ODD_OPEN : BCC_CT_OL_ODD_CLOSED;
    }
    else
    {
        mask = BCC_RW_CB_OL_ODD_MASK | BCC_RW_CB_OL_EVEN_MASK;
        command = (even == BCC_SWITCH_POS_OPEN) ? BCC_CB_OL_EVEN_OPEN : BCC_CB_OL_EVEN_CLOSED;
        command |= (odd == BCC_SWITCH_POS_OPEN) ? BCC_CB_OL_ODD_OPEN : BCC_CB_OL_ODD_CLOSED;
    }

    return BCC_Reg_Update(drvConfig, cid, BCC_REG_SYS_DIAG_ADDR, mask, command);
}

/*FUNCTION**********************************************************************
 *
 * Function Name : BCC_ConvertVoltageArray
 * Description   : This function computes voltage in uV from register raw
 *                 values.
 *
 *END**************************************************************************/
static inline void BCC_ConvertVoltageArray(const uint16_t rawMeas[],
    uint8_t size, uint32_t convMeas[])
{
    uint8_t i;

    for (i = 0U; i < size; i++)
    {
        convMeas[i] = BCC_GET_VOLT(rawMeas[i]);
    }
}

/*FUNCTION**********************************************************************
 *
 * Function Name : BCC_DiagOvuvPart
 * Description   : This function is part overvoltage and undervoltage functional
 *                 verification. It commands CT switches and measures voltage on
 *                 cells.
 *
 *END**************************************************************************/
static bcc_status_t BCC_DiagOvuvPart(bcc_drv_config_t* const drvConfig,
    bcc_cid_t cid, bcc_diag_switch_pos_t odd, bcc_diag_switch_pos_t even,
    uint32_t tauDiagN, uint16_t *fltOvrv, uint16_t *fltUndv)
{
    uint16_t fault[2];  /* Value of OV and UV fault registers. */
    bcc_status_t error = BCC_STATUS_SUCCESS;

    /* Clear OV, UV faults. */
    error = BCC_Reg_Write(drvConfig, cid, BCC_REG_CELL_OV_FLT_ADDR, 0x0000U, NULL);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    error = BCC_Reg_Write(drvConfig, cid, BCC_REG_CELL_UV_FLT_ADDR, 0x0000U, NULL);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* Command CTx switches. */
    error = BCC_CommandSwitches(drvConfig, cid, BCC_SWITCH_SEL_CT, odd, even);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* Wait for 5 times diagnostic time constant. */
    BCC_MCU_WaitUs(5U * tauDiagN);

    /* Initiate conversion. */
    error = BCC_RunCOD(drvConfig, cid);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* Read OV & UV flags. */
    error = BCC_Reg_Read(drvConfig, cid, BCC_REG_CELL_OV_FLT_ADDR, 2U, fault);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    (*fltOvrv) = fault[0];
    (*fltUndv) = fault[1];

    return BCC_STATUS_SUCCESS;
}

/*FUNCTION**********************************************************************
 *
 * Function Name : BCC_DiagCtxopenPart
 * Description   : This function is part of CTX open detect. It clear OV and UV
**                 fault registers, commands CT switches and measures voltage on
**                 cells.
 *
 *END**************************************************************************/
static bcc_status_t BCC_DiagCtxopenPart(bcc_drv_config_t* const drvConfig,
    bcc_cid_t cid, bcc_diag_switch_pos_t odd, bcc_diag_switch_pos_t even,
    uint32_t tauDiag, uint16_t *measVal)
{
    bcc_status_t error = BCC_STATUS_SUCCESS;

    /* Clear OV, UV faults. */
    error = BCC_Reg_Write(drvConfig, cid, BCC_REG_CELL_OV_FLT_ADDR, 0x0000U, NULL);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    error = BCC_Reg_Write(drvConfig, cid, BCC_REG_CELL_UV_FLT_ADDR, 0x0000U, NULL);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* Command fault detection switches. */
    error = BCC_CommandSwitches(drvConfig, cid, BCC_SWITCH_SEL_CT, odd, even);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* Wait for 5 times diagnostic time constant. */
    BCC_MCU_WaitUs(5U * tauDiag);

    /* Initiate conversion. */
    error = BCC_RunCOD(drvConfig, cid);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* Read conversion results. */
    if (drvConfig->device[(uint8_t)cid - 1] == BCC_DEVICE_MC33771)
    {
        error = BCC_Reg_Read(drvConfig, cid, BCC_REG_MEAS_CELLX_ADDR_MC33771_START,
                             BCC_MAX_CELLS_MC33771, measVal);
    }
    else
    {
        error = BCC_Reg_Read(drvConfig, cid, BCC_REG_MEAS_CELLX_ADDR_MC33772_START,
                             BCC_MAX_CELLS_MC33772, measVal);
    }

    return error;
}

/*FUNCTION**********************************************************************
 *
 * Function Name : BCC_DiagRunImeas
 * Description   : This function is part of current measurement diagnostics
 *                 (the first procedure). It resets Coulomb counter, initiates
 *                 conversion and reads measured current.
 *
 *END**************************************************************************/
static bcc_status_t BCC_DiagRunImeas(bcc_drv_config_t* const drvConfig,
    bcc_cid_t cid, int32_t *current)
{
    uint16_t measCurrentRaw[2];  /* Value of MEAS_ISENSE 1 and 2 registers */
    bcc_status_t error = BCC_STATUS_SUCCESS;

    /* 7a. Write ADC_CFG[CC_RST] = 1 to reset the Coulomb counter COULOMB_CNT.*/
    /* Reset Coulomb counter. */
    error = BCC_Reg_Update(drvConfig, cid, BCC_REG_ADC_CFG_ADDR, BCC_W_CC_RST_MASK, BCC_CC_RESET);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* 7b. Initiate conversion. */
    /* 8. Wait for the conversion time (16 bit resolution). */
    error = BCC_RunCOD(drvConfig, cid);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* 9. Read conversion results. */
    error = BCC_Reg_Read(drvConfig, cid, BCC_REG_MEAS_ISENSE1_ADDR, 2U, measCurrentRaw);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* Extracts current from measured raw value. */
    (*current) = BCC_GET_ISENSE_VOLT(measCurrentRaw[0], measCurrentRaw[1]);

    return BCC_STATUS_SUCCESS;
}

/*FUNCTION**********************************************************************
 *
 * Function Name : BCC_GpioOtUtPart
 * Description   : This function is part of GPIO OT and UT functional
 *                 verification.
 *
 *END**************************************************************************/
static bcc_status_t BCC_GpioOtUtPart(bcc_drv_config_t* const drvConfig,
    bcc_cid_t cid, uint16_t gpioCFG2Val, uint16_t *otUtStat)
{
    bcc_status_t error = BCC_STATUS_SUCCESS;

    /* 3. Set GPIO_CFG2[GPIOx_DR] register to drive output for overtemperature
     * or undertemperature. */
    error = BCC_Reg_Write(drvConfig, cid, BCC_REG_GPIO_CFG2_ADDR, gpioCFG2Val, NULL);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* 5. Initiate conversion. */
    error = BCC_RunCOD(drvConfig, cid);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* 6. Conversions below the TH_ANx_OT threshold trigger the ANx_OT fault bit.
     * Conversions above the TH_ANx_UT threshold trigger the ANx_UT fault bit. */
    error = BCC_Reg_Read(drvConfig, cid, BCC_REG_AN_OT_UT_FLT_ADDR, 1U, otUtStat);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* Clear OT/UT faults before exit. */
    return BCC_Reg_Write(drvConfig, cid, BCC_REG_AN_OT_UT_FLT_ADDR, 0x0000U, NULL);
}

/******************************************************************************
 * API
 ******************************************************************************/

/*FUNCTION**********************************************************************
 *
 * Function Name : BCC_Diag_ADC1
 * Description   : This function implements the ADC1-A and ADC1-B functional
 *                 verification.
 *
 *END**************************************************************************/
bcc_status_t BCC_Diag_ADC1(bcc_drv_config_t* const drvConfig, bcc_cid_t cid,
    bcc_diag_adc1x_res_t *results)
{
    uint8_t i;
    uint16_t fault2MaskVal, wakeUp2MaskVal, adcCfgVal;
    uint16_t measVal[BCC_DIAG_ADC1_MEAS_NUM][2];
    bcc_status_t error = BCC_STATUS_SUCCESS;

    BCC_MCU_Assert(drvConfig != NULL);
    BCC_MCU_Assert(results != NULL);

    if ((cid == BCC_CID_UNASSIG) || (((uint8_t)cid) > drvConfig->devicesCnt))
    {
        return BCC_STATUS_PARAM_RANGE;
    }

    /* Disable the ADC1_A and ADC1_B fault detection by setting FAULT_MASK2
       register, MASK_11_F = MASK_10_F = 1. */
    error = BCC_Reg_Read(drvConfig, cid, BCC_REG_FAULT_MASK2_ADDR, 1U, &fault2MaskVal);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    error = BCC_Reg_Write(drvConfig, cid, BCC_REG_FAULT_MASK2_ADDR,
                          fault2MaskVal | BCC_ADC1_A_FLT_DIS | BCC_ADC1_B_FLT_DIS, NULL);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* Disable the wake-up, due to ADC1_A and ADC1_B faults, by setting
       WAKEUP_MASK2 register, MASK_11_F = MASK_10_F = 1. */
    error = BCC_Reg_Read(drvConfig, cid, BCC_REG_WAKEUP_MASK2_ADDR, 1U, &wakeUp2MaskVal);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    error = BCC_Reg_Write(drvConfig, cid, BCC_REG_WAKEUP_MASK2_ADDR,
                          wakeUp2MaskVal | BCC_ADC1_A_WAKEUP_DIS | BCC_ADC1_B_WAKEUP_DIS, NULL);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* Detection performance can be guaranteed only if ADC_CFG[ADC1_A_DEF] =
       ADC_CFG[ADC1_B_DEF] = 11 (16 bit resolution). */
    error = BCC_Reg_Read(drvConfig, cid, BCC_REG_ADC_CFG_ADDR, 1U, &adcCfgVal);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    error = BCC_Reg_Update(drvConfig, cid, BCC_REG_ADC_CFG_ADDR,
                           BCC_RW_ADC1_A_DEF_MASK | BCC_RW_ADC1_B_DEF_MASK,
                           BCC_ADC1_A_RES_16BIT | BCC_ADC1_B_RES_16BIT);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* Measurement values (of an independent voltage reference) are be obtained
       by reading the MEAS_VBG_DIAG_ADC1A and MEAS_VBG_DIAG_ADC1B registers.
       It is necessary that the system controller performs a moving average of
       N consecutive values of MEAS_VBG_DIAG_ADC1A and N consecutive values of
       MEAS_VBG_DIAG_ADC1B before deciding about the fault. N shall be equal or
       greater than 6. */
    for (i = 0; i < BCC_DIAG_ADC1_MEAS_NUM; i++)
    {
        error = BCC_RunCOD(drvConfig, cid);
        if (error != BCC_STATUS_SUCCESS)
        {
            return error;
        }

        /* Read conversion results. */
        error = BCC_Reg_Read(drvConfig, cid, BCC_REG_MEAS_VBG_DIAG_ADC1A_ADDR, 2U, measVal[i]);
        if (error != BCC_STATUS_SUCCESS)
        {
            return error;
        }
    }

    /* Write back the original values of FAULT_MASK2, WAKEUP_MASK2 and
     * ADC_CFG registers. */
    error = BCC_Reg_Write(drvConfig, cid, BCC_REG_FAULT_MASK2_ADDR, fault2MaskVal, NULL);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    error = BCC_Reg_Write(drvConfig, cid, BCC_REG_WAKEUP_MASK2_ADDR, wakeUp2MaskVal, NULL);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    error = BCC_Reg_Write(drvConfig, cid, BCC_REG_ADC_CFG_ADDR, adcCfgVal, NULL);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* Evaluate results. */
    results->adc1aAvg = 0;
    results->adc1bAvg = 0;
    for (i = 0; i < BCC_DIAG_ADC1_MEAS_NUM; i++)
    {
        results->adc1aAvg += BCC_GET_VOLT(measVal[i][0]);
        results->adc1bAvg += BCC_GET_VOLT(measVal[i][1]);
    }
    results->adc1aAvg /= BCC_DIAG_ADC1_MEAS_NUM;
    results->adc1bAvg /= BCC_DIAG_ADC1_MEAS_NUM;

    if (BCC_IS_IN_RANGE(results->adc1aAvg, BCC_DIAG_V_BGP - BCC_DIAG_ADC1X_FV,
            BCC_DIAG_V_BGP + BCC_DIAG_ADC1X_FV) &&
        BCC_IS_IN_RANGE(results->adc1bAvg, BCC_DIAG_V_BGP - BCC_DIAG_ADC1X_FV,
            BCC_DIAG_V_BGP + BCC_DIAG_ADC1X_FV))
    {
        results->error = false;
    }
    else
    {
        results->error = true;
    }

    return BCC_STATUS_SUCCESS;
}

/*FUNCTION**********************************************************************
 *
 * Function Name : BCC_Diag_OvUv
 * Description   : This function implements OV/UV functional verification
 *                 through digital comparators against tunable thresholds.
 *
 *END**************************************************************************/
bcc_status_t BCC_Diag_OvUv(bcc_drv_config_t* const drvConfig, bcc_cid_t cid,
    bcc_battery_type_t battType, const bcc_diag_const_t* const diagTimeConst,
    bcc_diag_ov_uv_res_t* results)
{
    uint16_t regVal;      /* Value of a register. */
    uint16_t fault1MaskVal, thAllCtVal, ovUvEnVal;
    bcc_status_t error = BCC_STATUS_SUCCESS;

    BCC_MCU_Assert(drvConfig != NULL);
    BCC_MCU_Assert(results != NULL);
    BCC_MCU_Assert(diagTimeConst != NULL);

    if ((cid == BCC_CID_UNASSIG) || (((uint8_t)cid) > drvConfig->devicesCnt) || (battType > BCC_BATT_N))
    {
        return BCC_STATUS_PARAM_RANGE;
    }

    /* a. If the number of cells on the cluster is odd, then write the bit
     * SYS_CFG2[NUMB_ODD] to logic 1, else write it to logic 0. */
    regVal = (drvConfig->cellCnt[(uint8_t)cid - 1] & 0x01U) ? BCC_ODD_CELLS : BCC_EVEN_CELLS;
    error = BCC_Reg_Update(drvConfig, cid, BCC_REG_SYS_CFG2_ADDR, BCC_RW_NUMB_ODD_MASK, regVal);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* Mask simulated OV/UV faults - do not activate FAULT pin for these
     * faults. */
    error = BCC_Reg_Read(drvConfig, cid, BCC_REG_FAULT_MASK1_ADDR, 1U, &fault1MaskVal);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    error = BCC_Reg_Write(drvConfig, cid, BCC_REG_FAULT_MASK1_ADDR,
                          fault1MaskVal | BCC_CT_UV_FLT_DIS | BCC_CT_OV_FLT_DIS, NULL);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* 1. Enter diagnostic mode. */
    error = BCC_EnterDiagnostics(drvConfig, cid);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* Enable OV/UV. */
    error = BCC_Reg_Read(drvConfig, cid, BCC_REG_OV_UV_EN_ADDR, 1U, &ovUvEnVal);

    if (drvConfig->device[(uint8_t)cid - 1] == BCC_DEVICE_MC33771)
    {
        /* 2. Write OV_UV_EN[CTx_OVUV_EN] for x = 1..14 to enable OV/UV. */
        error = BCC_Reg_Write(drvConfig, cid, BCC_REG_OV_UV_EN_ADDR, 0xFFFFU, NULL);
    }
    else
    {
        /* 2. Write OV_UV_EN[CTx_OVUV_EN] for x = 1..6 to enable OV/UV. */
        error = BCC_Reg_Write(drvConfig, cid, BCC_REG_OV_UV_EN_ADDR, 0xC03FU, NULL);
    }
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* 3. Set the OV and the UV thresholds to diagnostic values (see CTx_OV_TH
     * and CTx_UV_TH parameters in reference document) */
    error = BCC_Reg_Read(drvConfig, cid, BCC_REG_TH_ALL_CT_ADDR, 1U, &thAllCtVal);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    if (battType == BCC_BATT_T)
    {
        regVal = BCC_SET_ALL_CT_OV_TH(BCC_DIAG_CTX_OV_TH_T) |
                 BCC_SET_ALL_CT_UV_TH(BCC_DIAG_CTX_UV_TH_T);
    }
    else if (battType == BCC_BATT_F)
    {
        regVal = BCC_SET_ALL_CT_OV_TH(BCC_DIAG_CTX_OV_TH_F) |
                 BCC_SET_ALL_CT_UV_TH(BCC_DIAG_CTX_UV_TH_F);
    }
    else
    {
        regVal = BCC_SET_ALL_CT_OV_TH(BCC_DIAG_CTX_OV_TH_N) |
                 BCC_SET_ALL_CT_UV_TH(BCC_DIAG_CTX_UV_TH_N);
    }

    error = BCC_Reg_Write(drvConfig, cid, BCC_REG_TH_ALL_CT_ADDR, regVal, NULL);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* 4. Enter OV and UV functional verification. */
    error = BCC_Reg_Update(drvConfig, cid, BCC_REG_SYS_DIAG_ADDR, BCC_RW_CT_OV_UV_MASK, BCC_CT_OV_UV_ENABLED);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* Command switches, run measurement and read results (odd switches opened,
     * even closed). */
    error = BCC_DiagOvuvPart(drvConfig, cid, BCC_SWITCH_POS_OPEN, BCC_SWITCH_POS_CLOSED,
                             diagTimeConst->tauDiagN, &(results->ovOdd), &(results->uvEven));
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* Command switches, run measurement and read results (odd switches closed,
     * even opened). */
    error = BCC_DiagOvuvPart(drvConfig, cid, BCC_SWITCH_POS_CLOSED, BCC_SWITCH_POS_OPEN,
                             diagTimeConst->tauDiagN, &(results->ovEven), &(results->uvOdd));
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* 16. Open all switches. */
    error = BCC_CommandSwitches(drvConfig, cid, BCC_SWITCH_SEL_CT,
                                BCC_SWITCH_POS_OPEN, BCC_SWITCH_POS_OPEN);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* Leave OV and UV functional verification. */
    error = BCC_Reg_Update(drvConfig, cid, BCC_REG_SYS_DIAG_ADDR,
                           BCC_RW_CT_OV_UV_MASK, BCC_CT_OV_UV_DISABLED);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* 17. Wait for 10 times measurement time constant. */
    BCC_MCU_WaitUs(10U * diagTimeConst->tau);

    /* 18. Restore normal functional values for the OV and UV thresholds. */
    error = BCC_Reg_Write(drvConfig, cid, BCC_REG_TH_ALL_CT_ADDR, thAllCtVal, NULL);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    error = BCC_Reg_Write(drvConfig, cid, BCC_REG_OV_UV_EN_ADDR, ovUvEnVal, NULL);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* 19. Clear OV, UV faults. */
    error = BCC_Reg_Write(drvConfig, cid, BCC_REG_CELL_OV_FLT_ADDR, 0x0000U, NULL);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    error = BCC_Reg_Write(drvConfig, cid, BCC_REG_CELL_UV_FLT_ADDR, 0x0000U, NULL);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* Unmask simulated faults (OV & UV). */
    error = BCC_Reg_Write(drvConfig, cid, BCC_REG_FAULT_MASK1_ADDR, fault1MaskVal, NULL);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* 20. Exit diagnostic mode. Automatically open all switches. */
    error = BCC_ExitDiagnostics(drvConfig, cid);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* Evaluate diagnostics. */
    if ((results->ovOdd & drvConfig->drvData.cellMap[(uint8_t)cid - 1]) != BCC_ODD_CELL_MASK(drvConfig, cid))
    {
        results->error = true;
    }
    else if ((results->ovEven & drvConfig->drvData.cellMap[(uint8_t)cid - 1]) != BCC_EVEN_CELL_MASK(drvConfig, cid))
    {
        results->error = true;
    }
    else if ((results->uvOdd & drvConfig->drvData.cellMap[(uint8_t)cid - 1]) != BCC_ODD_CELL_MASK(drvConfig, cid))
    {
        results->error = true;
    }
    else if ((results->uvEven & drvConfig->drvData.cellMap[(uint8_t)cid - 1]) != BCC_EVEN_CELL_MASK(drvConfig, cid))
    {
        results->error = true;
    }
    else
    {
        results->error = false;
    }

    return BCC_STATUS_SUCCESS;
}

/*FUNCTION**********************************************************************
 *
 * Function Name : BCC_Diag_CTxOpen
 * Description   : This function implements CTx open detection and functional
 *                 verification.
 *
 *END**************************************************************************/
bcc_status_t BCC_Diag_CTxOpen(bcc_drv_config_t* const drvConfig, bcc_cid_t cid,
    bcc_battery_type_t battType, const bcc_diag_const_t* const diagTimeConst,
    bcc_diag_ctx_open_res_t* results)
{
    uint8_t i;
    uint16_t measVolt[3][BCC_MAX_CELLS]; /* Measured voltages with use of
                                            different register settings. */
    uint16_t regVal;                     /* Value of a register. */
    uint32_t vOlDetect;
    bcc_device_t deviceType;
    bool odd;
    bcc_status_t error = BCC_STATUS_SUCCESS;

    BCC_MCU_Assert(drvConfig != NULL);
    BCC_MCU_Assert(results != NULL);
    BCC_MCU_Assert(diagTimeConst != NULL);

    if ((cid == BCC_CID_UNASSIG) || (((uint8_t)cid) > drvConfig->devicesCnt) || (battType > BCC_BATT_N))
    {
        return BCC_STATUS_PARAM_RANGE;
    }

    deviceType = drvConfig->device[(uint8_t)cid - 1];

    /* If the number of cells on the cluster is odd, then write the bit
     * SYS_CFG2[NUMB_ODD] to logic 1, else write it to logic 0. */
    regVal = (drvConfig->cellCnt[(uint8_t)cid - 1] & 0x01U) ? BCC_ODD_CELLS : BCC_EVEN_CELLS;
    error = BCC_Reg_Update(drvConfig, cid, BCC_REG_SYS_CFG2_ADDR, BCC_RW_NUMB_ODD_MASK, regVal);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* Mask simulated faults (OV & UV). */
    error = BCC_Reg_Update(drvConfig, cid, BCC_REG_FAULT_MASK1_ADDR,
                           BCC_R_CT_UV_FLT_MASK | BCC_R_CT_OV_FLT_MASK,
                           BCC_CT_UV_FLT_DIS | BCC_CT_OV_FLT_DIS);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* 1. Enter diagnostic mode. */
    error = BCC_EnterDiagnostics(drvConfig, cid);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* Initiate conversion. */
    error = BCC_RunCOD(drvConfig, cid);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* Read conversion results (pre-closure measurements). */
    if (deviceType == BCC_DEVICE_MC33771)
    {
        error = BCC_Reg_Read(drvConfig, cid, BCC_REG_MEAS_CELLX_ADDR_MC33771_START,
                             BCC_MAX_CELLS_MC33771, measVolt[0]);
    }
    else
    {
        error = BCC_Reg_Read(drvConfig, cid, BCC_REG_MEAS_CELLX_ADDR_MC33772_START,
                             BCC_MAX_CELLS_MC33772, measVolt[0]);
    }
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* 2 - 6. Command switches, run measurement and read results. */
    error = BCC_DiagCtxopenPart(drvConfig, cid, BCC_SWITCH_POS_CLOSED, BCC_SWITCH_POS_OPEN,
                                diagTimeConst->tauDiag, measVolt[1]);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* 8 - 12. Command switches, run measurement and read results. */
    error = BCC_DiagCtxopenPart(drvConfig, cid, BCC_SWITCH_POS_OPEN, BCC_SWITCH_POS_CLOSED,
                                diagTimeConst->tauDiag, measVolt[2]);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* 14. Open all switches. */
    error = BCC_CommandSwitches(drvConfig, cid, BCC_SWITCH_SEL_CT,
                                BCC_SWITCH_POS_OPEN, BCC_SWITCH_POS_OPEN);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* 15. Wait for 10 times measurement constant. */
    BCC_MCU_WaitUs(10U * diagTimeConst->tau);

    /* 16. Clear CELL_OV_FLT and CELL_UV_FLT fault registers, as well as
     * FAULT1_STATUS[CT_OV_FLT,CT_UV_FLT] bits. */
    error = BCC_Reg_Write(drvConfig, cid, BCC_REG_CELL_OV_FLT_ADDR, 0x0000U, NULL);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    error = BCC_Reg_Write(drvConfig, cid, BCC_REG_CELL_UV_FLT_ADDR, 0x0000U, NULL);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* Unmask simulated faults (OV & UV). */
    error = BCC_Reg_Update(drvConfig, cid, BCC_REG_FAULT_MASK1_ADDR,
                           BCC_R_CT_UV_FLT_MASK | BCC_R_CT_OV_FLT_MASK,
                           BCC_CT_UV_FLT_EN | BCC_CT_OV_FLT_EN);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* 17. Exit diagnostic mode. */
    error = BCC_ExitDiagnostics(drvConfig, cid);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* Evaluate / Publish results. */
    BCC_ConvertVoltageArray(measVolt[0], BCC_MAX_CELLS_DEV(deviceType), results->measPreClosure);
    BCC_ConvertVoltageArray(measVolt[1], BCC_MAX_CELLS_DEV(deviceType), results->measOddClosed);
    BCC_ConvertVoltageArray(measVolt[2], BCC_MAX_CELLS_DEV(deviceType), results->measEvenClosed);

    /* Determine fault mode:
     * Normal condition
     *   SWx OFF: V >= min(Vcell)
     *   SWx ON: V < TH_CTx[CTx_UV_TH]
     * Open line condition during test execution
     *   SWx OFF: No decision
     *   SWx ON: V < VOL_DETECT
     */
    results->ctxOpen = 0x0000U;

    if (battType == BCC_BATT_T)
    {
        vOlDetect = BCC_DIAG_V_OL_DETECT_T;
    }
    else if (battType == BCC_BATT_F)
    {
        vOlDetect = BCC_DIAG_V_OL_DETECT_F;
    }
    else
    {
        vOlDetect = BCC_DIAG_V_OL_DETECT_N;
    }

    odd = true;
    for (i = 0U; i < BCC_MAX_CELLS_DEV(deviceType); i ++)
    {
        if (BCC_IS_CELL_CONN(drvConfig, (uint8_t)cid, i + 1U))
        {
            if (odd)
            {
                /* Open detection on odd cells: Voltage on cell should not be
                 * below specified value in normal condition. */
                if (results->measOddClosed[BCC_MAX_CELLS_DEV(deviceType) - (i + 1)] < vOlDetect)
                {
                    results->ctxOpen |= (uint16_t)(1U << i);
                }
            }
            else
            {
                /* Open detection on even cells: Voltage on cell should not be
                 * below specified value in normal condition. */
                if (results->measEvenClosed[BCC_MAX_CELLS_DEV(deviceType) - (i + 1)] < vOlDetect)
                {
                    results->ctxOpen |= (uint16_t)(1U << i);
                }
            }

            odd = !odd;
        }
    }

    return BCC_STATUS_SUCCESS;
}

/*FUNCTION**********************************************************************
 *
 * Function Name : BCC_Diag_CellVolt
 * Description   : This function implements Cell Voltage Channel functional
 *                 verification.
 *
 *END**************************************************************************/
bcc_status_t BCC_Diag_CellVolt(bcc_drv_config_t* const drvConfig, bcc_cid_t cid,
    bcc_diag_cell_volt_res_t* results)
{
    uint8_t i, j;
    uint16_t adcCfgVal;   /* Original value of ADC_CFG register. */
    /* Measured cells voltage
     * MC33771: [0] CT14, ..., [13] CT1
     * MC33772: [0] CT6, ..., [5] CT1 */
    uint16_t measVolt[BCC_MAX_CELLS];
    /* Temporary measured cells voltages in uV
     * MC33771: [0] CT14, ..., [13] CT1
     * MC33772 ([0] CT6, ..., [5] CT1 */
    uint32_t measVoltUV[BCC_MAX_CELLS];
    bcc_device_t deviceType;
    bcc_status_t error = BCC_STATUS_SUCCESS;

    BCC_MCU_Assert(drvConfig != NULL);
    BCC_MCU_Assert(results != NULL);

    if ((cid == BCC_CID_UNASSIG) || (((uint8_t)cid) > drvConfig->devicesCnt))
    {
        return BCC_STATUS_PARAM_RANGE;
    }

    deviceType = drvConfig->device[(uint8_t)cid - 1];

    /* 1. Enter diagnostic mode. */
    error = BCC_EnterDiagnostics(drvConfig, cid);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* Configure 16 bit resolution of ADC1-A, B
     * (ADC_CFG[ADC1_A_DEF] = ADC_CFG[ADC1_B_DEF] = 11). */
    error = BCC_Reg_Read(drvConfig, cid, BCC_REG_ADC_CFG_ADDR, 1U, &adcCfgVal);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    error = BCC_Reg_Update(drvConfig, cid, BCC_REG_ADC_CFG_ADDR,
                           BCC_RW_ADC1_A_DEF_MASK | BCC_RW_ADC1_B_DEF_MASK,
                           BCC_ADC1_A_RES_16BIT | BCC_ADC1_B_RES_16BIT);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* 2. Isolate CTx inputs and places reference at amplifier input. */
    error = BCC_Reg_Update(drvConfig, cid, BCC_REG_SYS_DIAG_ADDR,
                           BCC_RW_DA_DIAG_MASK, BCC_DA_DIAG_CHECK);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* To reduce the effect of the noise, it is good practice to cycle through
     * steps 3 and 4 a few times to get an average of the results before
     * proceeding to step 5. */
    for (i = 0; i < BCC_DIAG_CVFV_MEAS_NUM; i++)
    {
        /* 3. Initiate conversion. */
        /* 4. Wait for conversion time. */
        error = BCC_RunCOD(drvConfig, cid);
        if (error != BCC_STATUS_SUCCESS)
        {
            return error;
        }

        /* 5a. The system controller reads MEAS_CELLx results */
        if (deviceType == BCC_DEVICE_MC33771)
        {
            error = BCC_Reg_Read(drvConfig, cid, BCC_REG_MEAS_CELLX_ADDR_MC33771_START,
                                 BCC_MAX_CELLS_MC33771, measVolt);
        }
        else
        {
            error = BCC_Reg_Read(drvConfig, cid, BCC_REG_MEAS_CELLX_ADDR_MC33772_START,
                                 BCC_MAX_CELLS_MC33772, measVolt);
        }
        if (error != BCC_STATUS_SUCCESS)
        {
            return error;
        }

        if (i == 0)
        {
            BCC_ConvertVoltageArray(measVolt, BCC_MAX_CELLS_DEV(deviceType), results->measCellVolt);
        }
        else
        {
            BCC_ConvertVoltageArray(measVolt, BCC_MAX_CELLS_DEV(deviceType), measVoltUV);

            for (j = 0; j < BCC_MAX_CELLS_DEV(deviceType); j++)
            {
                results->measCellVolt[j] += measVoltUV[j];
            }
        }
    }

    /* Compute the average of measurements. */
    for (j = 0; j < BCC_MAX_CELLS_DEV(deviceType); j++)
    {
        results->measCellVolt[j] /= BCC_DIAG_CVFV_MEAS_NUM;
    }

    /* 7. Clear cell OV faults in both the FAULT1_STATUS[CT_OV_FLT] bit and the CELL_OV_FLT register.
     * Note: FAULT1_STATUS[CT_OV_FLT] bit is automatically cleared by reset the CELL_OV_FLT. */
    error = BCC_Reg_Write(drvConfig, cid, BCC_REG_CELL_OV_FLT_ADDR, 0x0000U, NULL);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* Restore previous ADC settings. */
    error = BCC_Reg_Update(drvConfig, cid, BCC_REG_ADC_CFG_ADDR,
                           BCC_RW_ADC1_A_DEF_MASK | BCC_RW_ADC1_B_DEF_MASK, adcCfgVal);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* 8. Exit diagnostic mode. */
    error = BCC_ExitDiagnostics(drvConfig, cid);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* Evaluate / Publish results. */
    results->result = 0x0000U;
    if (deviceType == BCC_DEVICE_MC33771)
    {
        for (i = 3U; i <= 14U; i++)
        {
            /* 5b. The system controller computes errors
             * V_err_x = (MEAS_CELLx � MEAS_CELL2), for x = 3 to 14. */
            results->vErrX[i - 3] = (int32_t)results->measCellVolt[14 - i] - (int32_t)results->measCellVolt[12];

            /* 6. The system controller checks, for any x = 3 to 14, if
             * inequalities min(VCVFV) <= V_err_x <= max(VCVFV) are true.
             * If yes, the check is ok, otherwise an error is detected.
             * NOTE: Checking all channels from 3 to 14 is necessary only if
             * all 14 cells are used; otherwise, unused cell voltage
             * channels may be skipped. */
            if (BCC_IS_CELL_CONN(drvConfig, (uint8_t)cid, i))
            {
                if ((results->vErrX[i - 3] < BCC_DIAG_VCVFV_MIN) || (results->vErrX[i - 3] > BCC_DIAG_VCVFV_MAX))
                {
                    results->result |= (uint16_t)(1U << (i - 3));
                }
            }
        }
    }
    else
    {
        /* 5b. The system controller computes errors
         * V_err_x = (MEAS_CELLx + MEAS_CELLx+1)/2 -(MEAS_CELL1 + MEAS_CELL2)/2, for x= {3,5} */
        if (drvConfig->cellCnt[(uint8_t)cid - 1] <= 4)
        {
            /* NOTE: Checking all channels from 3 to 6 is necessary only if all
             * 6 cells are used; otherwise, unused cell voltage channels may be skipped. */
            results->vErrX[0] = 0;
        }
        else if (drvConfig->cellCnt[(uint8_t)cid - 1] == 5)
        {
            /* V_err_3 = (MEAS_CELL4) - (MEAS_CELL1 + MEAS_CELL2)/2 */
            results->vErrX[0] = ((int32_t)(results->measCellVolt[2])) -
                      ((int32_t)(results->measCellVolt[5] + results->measCellVolt[4]))/2;

        }
        else
        {
            /* V_err_3 = (MEAS_CELL3 + MEAS_CELL4)/2 - (MEAS_CELL1 + MEAS_CELL2)/2 */
            results->vErrX[0] =
                    (((int32_t)(results->measCellVolt[3] + results->measCellVolt[2])) -
                     ((int32_t)(results->measCellVolt[5] + results->measCellVolt[4])))/2;

        }

        if (drvConfig->cellCnt[(uint8_t)cid - 1] == 3)
        {
            /* V_err_5 = (MEAS_CELL6) - (MEAS_CELL1 + MEAS_CELL2)/2 */
            results->vErrX[1] = ((int32_t)(results->measCellVolt[0])) -
                                 ((int32_t)(results->measCellVolt[5] + results->measCellVolt[4]))/2;
        }
        else
        {
            /* V_err_5 = (MEAS_CELL5 + MEAS_CELL6)/2 - (MEAS_CELL1 + MEAS_CELL2)/2 */
            results->vErrX[1] = (((int32_t)(results->measCellVolt[1] + results->measCellVolt[0])) -
                                 ((int32_t)(results->measCellVolt[5] + results->measCellVolt[4])))/2;

        }

        /* 6. The system controller checks, for any x = {3,5}, if inequalities
         * min(VCVFV) <= V_err_x <= max(VCVFV) are true. If yes, the check is
         * ok, otherwise an error is detected. */
        for (i = 0; i < 2; i++)
        {
            if (!BCC_IS_IN_RANGE(results->vErrX[i], BCC_DIAG_VCVFV_MIN, BCC_DIAG_VCVFV_MAX))
            {
                results->result |= (uint16_t)(1U << i);
            }
        }
    }

    return BCC_STATUS_SUCCESS;
}

/*FUNCTION**********************************************************************
 *
 * Function Name : BCC_Diag_CellLeak
 * Description   : This function implements cell terminals and cell balancing
 *                 terminals leakage current diagnostic.
 *
 *END**************************************************************************/
bcc_status_t BCC_Diag_CellLeak(bcc_drv_config_t* const drvConfig, bcc_cid_t cid,
    bcc_diag_ctx_leak_res_t* results)
{
    uint8_t i, k;
    uint16_t measRaw[2][BCC_MAX_CELLS + 1]; /* Measured voltages in raw values for two configurations. */
    uint32_t measUV[2][BCC_MAX_CELLS + 1];  /* Measured voltages in uV for two configurations. */
    int32_t tmp;
    bcc_device_t deviceType;
    bcc_status_t error = BCC_STATUS_SUCCESS;

    BCC_MCU_Assert(drvConfig != NULL);
    BCC_MCU_Assert(results != NULL);

    if ((cid == BCC_CID_UNASSIG) || (((uint8_t)cid) > drvConfig->devicesCnt))
    {
        return BCC_STATUS_PARAM_RANGE;
    }

    deviceType = drvConfig->device[(uint8_t)cid - 1];

    /* A. detect a connector having an abnormally high contact resistance. */
    results->conResistance = 0x0000;

    /* 1. Set all CBx drivers to OFF. */
    for (i = 0; i < BCC_MAX_CELLS_DEV(deviceType); i++)
    {
        error = BCC_CB_SetIndividual(drvConfig, cid, i, false, 0);
        if (error != BCC_STATUS_SUCCESS)
        {
            return error;
        }
    }

    error = BCC_CB_Enable(drvConfig, cid, true);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* Cycle through all cells */
    for (i = 0; i < BCC_MAX_CELLS_DEV(deviceType); i++)
    {
        /* Measure the cell voltage Vcell_meas_x(CB_OFF) (this involves CTx and
         * CTx-1 pins, with CT0=CT_REF) */
        error = BCC_RunCOD(drvConfig, cid);
        if (error != BCC_STATUS_SUCCESS)
        {
            return error;
        }

        error = BCC_Reg_Read(drvConfig, cid, BCC_REG_MEAS_CELLX_ADDR_END - i, 1U, &(measRaw[0][i]));
        if (error != BCC_STATUS_SUCCESS)
        {
            return error;
        }

        /* Set the CBx to ON; */
        error = BCC_CB_SetIndividual(drvConfig, cid, i, true, 1);
        if (error != BCC_STATUS_SUCCESS)
        {
            return error;
        }

        /* Wait for the time interval TCTsettle=5ms (cell balance delay + cell
         * terminal settling time). */
        BCC_MCU_WaitMs(BCC_DIAG_TCT_SETTLE);

        /* Measure the cell voltage Vcell_meas_x(CB_ON); */
        error = BCC_RunCOD(drvConfig, cid);
        if (error != BCC_STATUS_SUCCESS)
        {
            return error;
        }

        error = BCC_Reg_Read(drvConfig, cid, BCC_REG_MEAS_CELLX_ADDR_END - i, 1U, &(measRaw[1][i]));
        if (error != BCC_STATUS_SUCCESS)
        {
            return error;
        }

        /* If |Vcell_meas_x(CB_ON) - Vcell_meas_x(CB_OFF)| > DV_diag, then error
         * detected. */
        tmp = ((int32_t)(BCC_GET_VOLT(measRaw[1][i]))) - ((int32_t)(BCC_GET_VOLT(measRaw[0][i])));
        if (!BCC_IS_IN_RANGE(tmp, -((int32_t)BCC_DIAG_DV), BCC_DIAG_DV))
        {
            results->conResistance |= (uint16_t)(1U << i);
        }

        /* Set the CBx to OFF; */
        error = BCC_CB_SetIndividual(drvConfig, cid, i, false, 1);
        if (error != BCC_STATUS_SUCCESS)
        {
            return error;
        }
    }
    /* Wait for the time interval TCTsettle=5ms (cell balance delay + cell
     * terminal settling time). */
    BCC_MCU_WaitMs(BCC_DIAG_TCT_SETTLE);

    /* B. Detect a leakage current. */
    /* 1. Enter diagnostic mode. */
    error = BCC_EnterDiagnostics(drvConfig, cid);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* 2. Pause cell balancing. */
    error = BCC_CB_Pause(drvConfig, cid, true);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* Averaging cycle with initialization */
    for (i = 0; i < BCC_MAX_CELLS_DEV(deviceType) + 1U; i++)
    {
        results->vleak_avx[i] = 0;
    }

    for (k = 0; k < BCC_DIAG_LEAK_MEAS_NUM; k++)
    {
        /* 3. Write SYS_DIAG[CT_LEAK_DIAG,POLARITY] = 10 to route cell terminal
         * and balancing pins according to the logic of the routing table. */
        error = BCC_Reg_Update(drvConfig, cid, BCC_REG_SYS_DIAG_ADDR,
                               BCC_RW_CT_LEAK_DIAG_MASK | BCC_RW_POLARITY_MASK,
                               BCC_CT_LEAK_DIAG_DIFF | BCC_POL_NON_INVERTED);
        if (error != BCC_STATUS_SUCCESS)
        {
            return error;
        }

        /* 4. Initiate conversion. */
        /* 5. Wait for conversion time. */
        error = BCC_RunCOD(drvConfig, cid);
        if (error != BCC_STATUS_SUCCESS)
        {
            return error;
        }

        /* 6. Read measurement results. */
        /* Read MEAS_STACK */
        error = BCC_Reg_Read(drvConfig, cid, BCC_REG_MEAS_STACK_ADDR, 1U, measRaw[0]);
        if (error != BCC_STATUS_SUCCESS)
        {
            return error;
        }

        /* Read MEAS_CELL14 (MEAS_CELL6) to MEAS_CELL1 */
        if (deviceType == BCC_DEVICE_MC33771)
        {
            error = BCC_Reg_Read(drvConfig, cid, BCC_REG_MEAS_CELLX_ADDR_MC33771_START,
                                 BCC_MAX_CELLS_MC33771, (uint16_t *)(measRaw[0] + 1));
        }
        else
        {
            error = BCC_Reg_Read(drvConfig, cid, BCC_REG_MEAS_CELLX_ADDR_MC33772_START,
                                 BCC_MAX_CELLS_MC33772, (uint16_t *)(measRaw[0] + 1));
        }
        if (error != BCC_STATUS_SUCCESS)
        {
            return error;
        }

        /* 7. Compute leakage indices IND0x = MEAS_CELLx, for x = 1 to 6/14. */
        /* 8. Compute leakage index IND07/15 = MEAS_STACK. */
        BCC_ConvertVoltageArray(measRaw[0], BCC_MAX_CELLS_DEV(deviceType) + 1U, measUV[0]);

        /* 9. Write SYS_DIAG[CT_LEAK_DIAG,POLARITY] = 11 to route cell terminal
         * and balancing pins according to the logic of the routing table. */
        error = BCC_Reg_Update(drvConfig, cid, BCC_REG_SYS_DIAG_ADDR,
                               BCC_RW_CT_LEAK_DIAG_MASK | BCC_RW_POLARITY_MASK,
                               BCC_CT_LEAK_DIAG_DIFF | BCC_POL_INVERTED);
        if (error != BCC_STATUS_SUCCESS)
        {
            return error;
        }

        /* 10. Initiate conversion. */
        /* 11. Wait for conversion time. */
        error = BCC_RunCOD(drvConfig, cid);
        if (error != BCC_STATUS_SUCCESS)
        {
            return error;
        }

        /* 12. Read measurement results. */
        /* Read MEAS_STACK */
        error = BCC_Reg_Read(drvConfig, cid, BCC_REG_MEAS_STACK_ADDR, 1U, measRaw[1]);
        if (error != BCC_STATUS_SUCCESS)
        {
            return error;
        }

        /* Read MEAS_CELL14 (MEAS_CELL6) to MEAS_CELL1 */
        if (deviceType == BCC_DEVICE_MC33771)
        {
            error = BCC_Reg_Read(drvConfig, cid, BCC_REG_MEAS_CELLX_ADDR_MC33771_START,
                                 BCC_MAX_CELLS_MC33771, (uint16_t *)(measRaw[1] + 1));
        }
        else
        {
            error = BCC_Reg_Read(drvConfig, cid, BCC_REG_MEAS_CELLX_ADDR_MC33772_START,
                                 BCC_MAX_CELLS_MC33772, (uint16_t *)(measRaw[1] + 1));
        }
        if (error != BCC_STATUS_SUCCESS)
        {
            return error;
        }

        /* 13. Compute leakage indices IND1x = MEAS_CELLx, for x = 1 to 6/14. */
        /* 14. Compute leakage index IND17/15 = MEAS_STACK. */
        BCC_ConvertVoltageArray(measRaw[1], BCC_MAX_CELLS_DEV(deviceType) + 1U, measUV[1]);

        /* 15. MC33771: Compute Vleakx(k) = IND0x + IND1x, for x = 1 to 15.
         *     MC33772: Compute Vleakx(k) = MAX(IND0x, IND1x), for x = 1 to 7.*/
        /* 16. Calculate Vleak_AVx(k)=1/k*Vleakx(k)+(1-1/k)*Vleak_AVx(k-1),
         * for x=1 to 7/15 (averaging) */
        for (i = 0; i < BCC_MAX_CELLS_DEV(deviceType) + 1U; i++)
        {
            if (deviceType == BCC_DEVICE_MC33771)
            {
                results->vleak_avx[BCC_MAX_CELLS_DEV(deviceType) - i] += measUV[0][i] + measUV[1][i];
            }
            else
            {
                results->vleak_avx[BCC_MAX_CELLS_DEV(deviceType) - i] +=
                        (measUV[0][i] >= measUV[1][i]) ? measUV[0][i] : measUV[1][i];
            }
        }
    }

    /* Fill results->leakStatus */
    results->leakStatus = 0x00U;
    for (i = 0; i < BCC_MAX_CELLS_DEV(deviceType) + 1U; i++)
    {
        /* 16. Calculate Vleak_AVx(k)=1/k*Vleakx(k)+(1-1/k)*Vleak_AVx(k-1),
         * for x=1 to 7/15 (averaging) */
        results->vleak_avx[i] /= BCC_DIAG_LEAK_MEAS_NUM;

        /* 17.Evaluate the decision criterion, for x = 1 to 7/15:
         *    If Vleak_AVx(k) >= VLEAK then cell x is leaky, else cell x is not leaky. */
        if (results->vleak_avx[i] >
            ((deviceType == BCC_DEVICE_MC33771) ? BCC_DIAG_VLEAK_MC33771 : BCC_DIAG_VLEAK_MC33772))
        {
            results->leakStatus |=  (uint16_t)(1U << i);
        }
    }

    /* 18. Clear OV, UV faults. */
    error = BCC_Reg_Write(drvConfig, cid, BCC_REG_CELL_OV_FLT_ADDR, 0x0000U, NULL);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    error = BCC_Reg_Write(drvConfig, cid, BCC_REG_CELL_UV_FLT_ADDR, 0x0000U, NULL);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* 19. Restore cell muxing settings. */
    error = BCC_Reg_Update(drvConfig, cid, BCC_REG_SYS_DIAG_ADDR,
                           BCC_RW_CT_LEAK_DIAG_MASK | BCC_RW_POLARITY_MASK,
                           BCC_CT_LEAK_DIAG_NORMAL | BCC_POL_NON_INVERTED);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* 20. Unpause cell balancing. */
    error = BCC_CB_Pause(drvConfig, cid, false);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* 21. Exit diagnostic mode. */
    return BCC_ExitDiagnostics(drvConfig, cid);
}

/*FUNCTION**********************************************************************
 *
 * Function Name : BCC_Diag_CurrentMeas
 * Description   : This function implements current measurement diagnostics.
 *
 *END**************************************************************************/
bcc_status_t BCC_Diag_CurrentMeas(bcc_drv_config_t* const drvConfig,
    bcc_cid_t cid, bcc_diag_current_meas_t sel, int32_t* current, bool* fault)
{
    uint16_t sysCfg1Val;  /* Original value of SYS_CFG1 register. */
    uint16_t sysDiagVal;  /* Original value of SYS_DIAG register. */
    uint16_t adcCfgVal;   /* Original value of ADC_CFG register. */
    /* PGA gain and ADC2 resolution for each bcc_diag_current_meas_t diagnostic
     * measurement. */
    uint16_t adcCfg[3] = {
            BCC_ADC2_RES_16BIT | BCC_ADC2_PGA_256,
            BCC_ADC2_RES_16BIT | BCC_ADC2_PGA_4,
            BCC_ADC2_RES_16BIT | BCC_ADC2_PGA_256
    };
    /* Inputs to PGA for each bcc_diag_current_meas_t diagnostic measurement. */
    uint16_t adcMux[3] = {
            BCC_IMUX_PGA_ZERO,
            BCC_IMUX_DIFF_REF,
            BCC_IMUX_GPIO5_6
    };
    bcc_status_t error = BCC_STATUS_SUCCESS;

    BCC_MCU_Assert(drvConfig != NULL);
    BCC_MCU_Assert(current != NULL);
    BCC_MCU_Assert(fault != NULL);

    if ((cid == BCC_CID_UNASSIG) || (((uint8_t)cid) > drvConfig->devicesCnt))
    {
        return BCC_STATUS_PARAM_RANGE;
    }

    if ((sel != BCC_DCM_AMP_INP_GND) && (sel != BCC_DCM_VREF_GAIN4) && (sel != BCC_DCM_AN5AN6))
    {
        return BCC_STATUS_PARAM_RANGE;
    }

    /* Store original values of SYS_CFG1, SYS_DIAG and ADC_CFG registers. */
    error = BCC_Reg_Read(drvConfig, cid, BCC_REG_SYS_CFG1_ADDR, 1U, &sysCfg1Val);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    error = BCC_Reg_Read(drvConfig, cid, BCC_REG_SYS_DIAG_ADDR, 1U, &sysDiagVal);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    error = BCC_Reg_Read(drvConfig, cid, BCC_REG_ADC_CFG_ADDR, 1U, &adcCfgVal);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* 1. Disable current measurement. */
    error = BCC_Reg_Update(drvConfig, cid, BCC_REG_SYS_CFG1_ADDR,
                           BCC_RW_I_MEAS_EN_MASK, BCC_I_MEAS_DISABLED);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* 2. Enter diagnostic mode. */
    error = BCC_EnterDiagnostics(drvConfig, cid);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* Set ADC2 measurement resolution and PGA gain */
    error = BCC_Reg_Update(drvConfig, cid, BCC_REG_ADC_CFG_ADDR,
                           BCC_RW_ADC2_DEF_MASK | BCC_W_PGA_GAIN_MASK, adcCfg[sel]);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* 3. Read the Coulomb counter register COULOMB_CNT to retain count information.
     * *** up to user *** */

    /* 4. Configure the current measurement chain for the specific diagnostic
     * source by writing to the SYS_DIAG[I_MUX] bits. */
    error = BCC_Reg_Update(drvConfig, cid, BCC_REG_SYS_DIAG_ADDR, BCC_RW_I_MUX_MASK, adcMux[sel]);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* 5. Enable the current measurement by setting SYS_CFG1[I_MEAS_EN] = 1. */
    error = BCC_Reg_Update(drvConfig, cid, BCC_REG_SYS_CFG1_ADDR,
                           BCC_RW_I_MEAS_EN_MASK, BCC_I_MEAS_ENABLED);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* 6. Wait for the time to perform auto-zero procedure t_AZC_SETTLE. */
    BCC_MCU_WaitUs(BCC_DIAG_T_AZC_SETTLE);

    /* 7. Write ADC_CFG[CC_RST] = 1 and ADC_CFG[SOC] = 1 to reset the coulomb
     * counter COULOMB_CNT and initiate a conversion. */
    /* 8. Wait for the conversion time. */
    /* 9. Read the conversion results in register MEAS_ISENSE1 and
     * MEAS_ISENSE2. */
    error = BCC_DiagRunImeas(drvConfig, cid, current);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* 9. Evaluate / Publish results. */
    if (sel == BCC_DCM_AMP_INP_GND)
    {
        /* When the measured value <= Voff_diag -> success. */
       (*fault) = ((*current) > (int32_t)BCC_DIAG_VOFF_MAX);
    }
    else if (sel == BCC_DCM_VREF_GAIN4)
    {
        /* When the measured value is in the range defined by Vref_diag then
         * success. */
        if (drvConfig->device[(uint8_t)cid - 1] == BCC_DEVICE_MC33771)
        {
            (*fault) = (((*current) < BCC_DIAG_VREF_MIN_MC33771) || ((*current) > BCC_DIAG_VREF_MAX_MC33771));
        }
        else
        {
            (*fault) = (((*current) < BCC_DIAG_VREF_MIN_MC33772) || ((*current) > BCC_DIAG_VREF_MAX_MC33772));
        }
    }
    else
    {
        /* sel == BCC_DCM_AN5AN6 */
        /* Result is application dependent. */
        (*fault) = false;
    }

    /* 10. Exit diagnostic mode. */
    error = BCC_ExitDiagnostics(drvConfig, cid);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* Restore original values of registers. */
    error = BCC_Reg_Write(drvConfig, cid, BCC_REG_SYS_CFG1_ADDR, sysCfg1Val, NULL);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    error = BCC_Reg_Write(drvConfig, cid, BCC_REG_SYS_DIAG_ADDR, sysDiagVal, NULL);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* 11. Reset Coulomb counter and restore previous ADC2 settings
     * (do not change TAG_ID field). */
    return BCC_Reg_Update(drvConfig, cid, BCC_REG_ADC_CFG_ADDR,
                          (uint16_t)(~BCC_RW_TAG_ID_MASK), adcCfgVal | BCC_CC_RESET);
}

/*FUNCTION**********************************************************************
 *
 * Function Name : BCC_Diag_ShuntConn
 * Description   : This function verifies whether the shunt resistor is properly
 *                 connected to the current channel low-pass filter.
 *
 *END**************************************************************************/
bcc_status_t BCC_Diag_ShuntConn(bcc_drv_config_t* const drvConfig,
    bcc_cid_t cid, const bcc_diag_const_t* const diagTimeConst, bool* shuntConn)
{
    uint16_t regVal; /* Value of FAULT1_STATUS register. */
    bcc_status_t error = BCC_STATUS_SUCCESS;

    BCC_MCU_Assert(drvConfig != NULL);
    BCC_MCU_Assert(diagTimeConst != NULL);
    BCC_MCU_Assert(shuntConn != NULL);

    if ((cid == BCC_CID_UNASSIG) || (((uint8_t)cid) > drvConfig->devicesCnt))
    {
        return BCC_STATUS_PARAM_RANGE;
    }

    /* 1. Disable current measurement. */
    error = BCC_Reg_Update(drvConfig, cid, BCC_REG_SYS_CFG1_ADDR,
                           BCC_RW_I_MEAS_EN_MASK, BCC_I_MEAS_DISABLED);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* Clear faults. */
    error = BCC_Reg_Write(drvConfig, cid, BCC_REG_FAULT1_STATUS_ADDR, 0x0000U, NULL);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* 2. Read the Coulomb counter COULOMB_CNT to retain count information
     * *** up to user *** */

    /* 3. Enter diagnostic mode. */
    error = BCC_EnterDiagnostics(drvConfig, cid);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* 4. Configure current measurement chain for the open detection check. */
    error = BCC_Reg_Update(drvConfig, cid, BCC_REG_SYS_DIAG_ADDR,
                           BCC_RW_ISENSE_OL_DIAG_MASK, BCC_ISENSE_OL_DIAG_ENABLED);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* 5. Wait for the diagnostic time constant. */
    BCC_MCU_WaitUs(diagTimeConst->tDiag);

    /* 6. Read FAULT1_STATUS[IS_OL_FLT] flag. */
    error = BCC_Reg_Read(drvConfig, cid, BCC_REG_FAULT1_STATUS_ADDR, 1U, &regVal);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* 7. Configure current measurement chain for the open detection check
     * by setting SYS_DIAG[I_SENSE_OL_DIAG] to logic 0. */
    error = BCC_Reg_Update(drvConfig, cid, BCC_REG_SYS_DIAG_ADDR,
                           BCC_RW_ISENSE_OL_DIAG_MASK, BCC_ISENSE_OL_DIAG_DISABLED);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* 8. Wait for 10 times the current measurement time constant (tau_i). */
    BCC_MCU_WaitUs(diagTimeConst->tauI);

    /* 9. Enable the current measurement and exit diagnostic mode by setting
     * SYS_CFG[I_MEAS_EN] to logic 1 and SYS_CFG1[GO2DIAG] to logic 0. */
    error = BCC_Reg_Update(drvConfig, cid, BCC_REG_SYS_CFG1_ADDR,
                           BCC_W_GO2DIAG_MASK | BCC_RW_I_MEAS_EN_MASK,
                           BCC_DIAG_MODE_DISABLED | BCC_I_MEAS_ENABLED);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* 10. Reset Coulomb counter. */
    error = BCC_Reg_Update(drvConfig, cid, BCC_REG_ADC_CFG_ADDR,
                           BCC_W_CC_RST_MASK, BCC_CC_RESET);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* Evaluate / Publish results. */
    (*shuntConn) = (regVal & BCC_RW_IS_OL_FLT_MASK) == 0x00U;

    /* Clear faults. */
    return BCC_Reg_Write(drvConfig, cid, BCC_REG_FAULT1_STATUS_ADDR, 0x0000U, NULL);
}

/*FUNCTION**********************************************************************
 *
 * Function Name : BCC_Diag_GPIOxOtUt
 * Description   : This function implements GPIOx OT/UT functional verification.
 *
 *END**************************************************************************/
bcc_status_t BCC_Diag_GPIOxOtUt(bcc_drv_config_t* const drvConfig,
    bcc_cid_t cid, bcc_diag_gpiox_otut_res_t* results)
{
    uint16_t regVal;          /* Value of a GPIO_CFGx register. */
    uint16_t gpioCfg1Val;     /* Original value of GPIO_CFG1 register. */
    uint16_t gpioCfg2Val;     /* Original value of GPIO_CFG2 register. */
    bcc_status_t error = BCC_STATUS_SUCCESS;

    BCC_MCU_Assert(drvConfig != NULL);
    BCC_MCU_Assert(results != NULL);

    if ((cid == BCC_CID_UNASSIG) || (((uint8_t)cid) > drvConfig->devicesCnt))
    {
        return BCC_STATUS_PARAM_RANGE;
    }

    /* Clear OT/UT faults. */
    error = BCC_Reg_Write(drvConfig, cid, BCC_REG_AN_OT_UT_FLT_ADDR, 0x0000U, NULL);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* 1. Enter diagnostic mode. */
    error = BCC_EnterDiagnostics(drvConfig, cid);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* Store original value of GPIO_CFG1 register. */
    error = BCC_Reg_Read(drvConfig, cid, BCC_REG_GPIO_CFG1_ADDR, 1U, &gpioCfg1Val);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* Store original value of GPIO_CFG2 register. */
    error = BCC_Reg_Read(drvConfig, cid, BCC_REG_GPIO_CFG2_ADDR, 1U, &gpioCfg2Val);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* 2. Optionally program GPIOx to be tested as analog through
     * GPIO_CFG1[GPIOx_CFG] register. In Diagnostic mode, only GPIOx configured
     * as analog inputs have buffers activated by the ANx_TEMP_DIAG bit. */
    regVal = BCC_GPIOX_AN_IN_RM_MEAS(0U) | BCC_GPIOX_AN_IN_RM_MEAS(1U) |
             BCC_GPIOX_AN_IN_RM_MEAS(2U) | BCC_GPIOX_AN_IN_RM_MEAS(3U) |
             BCC_GPIOX_AN_IN_RM_MEAS(4U) | BCC_GPIOX_AN_IN_RM_MEAS(5U) |
             BCC_GPIOX_AN_IN_RM_MEAS(6U);
    error = BCC_Reg_Write(drvConfig, cid, BCC_REG_GPIO_CFG1_ADDR, regVal, NULL);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* 4. Enable the GPIOx output buffer through the SYS_DIAG[ANx_TEMP_DIAG]. */
    error = BCC_Reg_Update(drvConfig, cid, BCC_REG_SYS_DIAG_ADDR,
                           BCC_RW_ANX_TEMP_DIAG_MASK, BCC_ANX_DIAG_SW_CLOSED);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* 3. Drive output high to emulate UT.
     * Note: it is valid to use GPIOx_DR bit in diagnostic mode and analog inpu mode of GPIOs. */
    /* 5. Request BCC to perform a conversion sequence by writing to ADC_CFG register. */
    /* 6. Conversions below the TH_ANx_OT threshold trigger the ANx_OT fault bit.
     * Conversions above the TH_ANx_UT threshold trigger the ANx_UT fault bit. */
    regVal = BCC_GPIOx_HIGH(0U) | BCC_GPIOx_HIGH(1U) | BCC_GPIOx_HIGH(2U) |
             BCC_GPIOx_HIGH(3U) | BCC_GPIOx_HIGH(4U) | BCC_GPIOx_HIGH(5U) |
             BCC_GPIOx_HIGH(6U);

    error = BCC_GpioOtUtPart(drvConfig, cid, regVal, &(results->untStat));
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* 3. Drive output low to emulate OT. */
    /* 5. Request BCC to perform a conversion sequence by writing to ADC_CFG register. */
    /* 6. Conversions below the TH_ANx_OT threshold trigger the ANx_OT fault bit.
     * Conversions above the TH_ANx_UT threshold trigger the ANx_UT fault bit. */
    regVal = BCC_GPIOx_LOW(0U) | BCC_GPIOx_LOW(1U) | BCC_GPIOx_LOW(2U) |
             BCC_GPIOx_LOW(3U) | BCC_GPIOx_LOW(4U) | BCC_GPIOx_LOW(5U) |
             BCC_GPIOx_LOW(6U);
    error = BCC_GpioOtUtPart(drvConfig, cid, regVal, &(results->ovtStat));
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* Restore original content of registers. */
    error = BCC_Reg_Write(drvConfig, cid, BCC_REG_GPIO_CFG2_ADDR, gpioCfg2Val, NULL);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* Disable GPIOx output buffer. */
    error = BCC_Reg_Update(drvConfig, cid, BCC_REG_SYS_DIAG_ADDR,
                           BCC_RW_ANX_TEMP_DIAG_MASK, BCC_ANX_DIAG_SW_OPEN);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    error = BCC_Reg_Write(drvConfig, cid, BCC_REG_GPIO_CFG1_ADDR, gpioCfg1Val, NULL);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* 7. Exit diagnostic mode. */
    error = BCC_ExitDiagnostics(drvConfig, cid);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* Evaluate the results. */
    if ((results->untStat & (BCC_RW_AN_UT_MASK | BCC_RW_AN_OT_MASK)) != BCC_RW_AN_UT_MASK)
    {
        results->error = true;
    }
    else if ((results->ovtStat & (BCC_RW_AN_UT_MASK | BCC_RW_AN_OT_MASK)) != BCC_RW_AN_OT_MASK)
    {
        results->error = true;
    }
    else
    {
        results->error = false;
    }

    return BCC_STATUS_SUCCESS;
}

/*FUNCTION**********************************************************************
 *
 * Function Name : BCC_Diag_GPIOxOpen
 * Description   : This function implements GPIOx open terminal diagnostics.
 *
 *END**************************************************************************/
bcc_status_t BCC_Diag_GPIOxOpen(bcc_drv_config_t* const drvConfig,
    bcc_cid_t cid, uint16_t* openStatus)
{
    uint16_t regVal;       /* Value of GPIO_CFG1 register. */
    uint16_t gpioCfg1Val;  /* Origin value of GPIO_CFG1 register. */
    bcc_status_t error = BCC_STATUS_SUCCESS;

    BCC_MCU_Assert(drvConfig != NULL);
    BCC_MCU_Assert(openStatus != NULL);

    if ((cid == BCC_CID_UNASSIG) || (((uint8_t)cid) > drvConfig->devicesCnt))
    {
        return BCC_STATUS_PARAM_RANGE;
    }

    /* Clear GPIOx short flags. */
    error = BCC_Reg_Write(drvConfig, cid, BCC_REG_GPIO_SHORT_ADDR, 0x0000U, NULL);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* 1. Enter diagnostic mode. */
    error = BCC_EnterDiagnostics(drvConfig, cid);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* Store original value of GPIO_CFG1 register. */
    error = BCC_Reg_Read(drvConfig, cid, BCC_REG_GPIO_CFG1_ADDR, 1U, &gpioCfg1Val);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* 2. Program GPIOx to be tested as analog input. */
    regVal = BCC_GPIOX_AN_IN_ABS_MEAS(0U) | BCC_GPIOX_AN_IN_ABS_MEAS(1U) |
             BCC_GPIOX_AN_IN_ABS_MEAS(2U) | BCC_GPIOX_AN_IN_ABS_MEAS(3U) |
             BCC_GPIOX_AN_IN_ABS_MEAS(4U) | BCC_GPIOX_AN_IN_ABS_MEAS(5U) |
             BCC_GPIOX_AN_IN_ABS_MEAS(6U);
    error = BCC_Reg_Write(drvConfig, cid, BCC_REG_GPIO_CFG1_ADDR, regVal, NULL);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* 3. Activate GPIOx weak pull-down.
     * Note: In Diagnostic mode, only GPIOx configured analog have a weak
     * pull-down activated by the ANx_OL_DIAG bit. */
    error = BCC_Reg_Update(drvConfig, cid, BCC_REG_SYS_DIAG_ADDR,
                           BCC_RW_ANX_OLDIAG_MASK, BCC_ANX_OL_DIAG_ENABLED);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* 4. Voltages below the VOL(TH) threshold set the GPIO_SHORT_AN_OPEN_STS
     * [ANx_OPEN] bit and the FAULT2_STATUS[AN_OPEN_FLT] fault bit. */
    error = BCC_Reg_Read(drvConfig, cid, BCC_REG_GPIO_SHORT_ADDR, 1U, openStatus);
    *openStatus &= BCC_RW_AN_OPEN_MASK;
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* Restore original content of registers. */
    error = BCC_Reg_Write(drvConfig, cid, BCC_REG_GPIO_CFG1_ADDR, gpioCfg1Val, NULL);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* 5. Deactivate GPIOx weak pull-down. */
    error = BCC_Reg_Update(drvConfig, cid, BCC_REG_SYS_DIAG_ADDR,
                           BCC_RW_ANX_OLDIAG_MASK, BCC_ANX_OL_DIAG_DISABLED);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* 6. Exit diagnostic mode. */
    error = BCC_ExitDiagnostics(drvConfig, cid);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* Clear GPIOx short flags. */
    return BCC_Reg_Write(drvConfig, cid, BCC_REG_GPIO_SHORT_ADDR, 0x0000U, NULL);
}

/*FUNCTION**********************************************************************
 *
 * Function Name : BCC_Diag_CBxOpen
 * Description   : This function implements Cell balance open load detection.
 *
 *END**************************************************************************/
bcc_status_t BCC_Diag_CBxOpen(bcc_drv_config_t* const drvConfig, bcc_cid_t cid,
    bcc_diag_cbx_open_res_t* results)
{
    uint16_t regVal;      /* Value of SYS_CFG2[NUMB_ODD]. */
    bcc_status_t error = BCC_STATUS_SUCCESS;

    BCC_MCU_Assert(drvConfig != NULL);
    BCC_MCU_Assert(results != NULL);

    if ((cid == BCC_CID_UNASSIG) || (((uint8_t)cid) > drvConfig->devicesCnt))
    {
        return BCC_STATUS_PARAM_RANGE;
    }

    /* 1. Enter diagnostic mode. */
    error = BCC_EnterDiagnostics(drvConfig, cid);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* 2. If the number of cells on the cluster is odd, then write the bit
     * SYS_CFG2[NUMB_ODD] to logic 1, else write it to logic 0. */
    regVal = (drvConfig->cellCnt[(uint8_t)cid - 1] & 0x01U) ? BCC_ODD_CELLS : BCC_EVEN_CELLS;
    error = BCC_Reg_Update(drvConfig, cid, BCC_REG_SYS_CFG2_ADDR, BCC_RW_NUMB_ODD_MASK, regVal);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* 3. Pause cell balancing by setting SYS_CFG1[CB_MANUAL_PAUSE] to logic 1. */
    error = BCC_CB_Pause(drvConfig, cid, true);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* 4. Command the SYS_DIAG[CB_OL_ODD,CB_OL_EVEN] field to the 10 configuration. */
    error = BCC_CommandSwitches(drvConfig, cid, BCC_SWITCH_SEL_CB,
                                BCC_SWITCH_POS_CLOSED, BCC_SWITCH_POS_OPEN);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* 5. Wait for the time delay t_delay. */
    BCC_MCU_WaitUs(BCC_DIAG_T_DELAY);

    /* 6. Read CB_OPEN_FLT to determine all CBx_OPEN_FLT bits. */
    error = BCC_Reg_Read(drvConfig, cid, BCC_REG_CB_OPEN_FLT_ADDR, 1U, &(results->cbxOpenStatusEven));
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* 7. Command the SYS_DIAG[CB_OL_ODD,CB_OL_EVEN] field to the 01 configuration. */
    error = BCC_CommandSwitches(drvConfig, cid, BCC_SWITCH_SEL_CB,
                                BCC_SWITCH_POS_OPEN, BCC_SWITCH_POS_CLOSED);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* 8. Wait for the time delay t_delay. */
    BCC_MCU_WaitUs(BCC_DIAG_T_DELAY);

    /* 9. Read CB_OPEN_FLT to determine all CBx_OPEN_FLT bits. */
    error = BCC_Reg_Read(drvConfig, cid, BCC_REG_CB_OPEN_FLT_ADDR, 1U, &(results->cbxOpenStatusOdd));
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* 10. Command the SYS_DIAG[CB_OL_ODD,CB_OL_EVEN] field to the 00 configuration. */
    error = BCC_CommandSwitches(drvConfig, cid, BCC_SWITCH_SEL_CB,
                               BCC_SWITCH_POS_OPEN, BCC_SWITCH_POS_OPEN);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* 11. Unpause cell balancing. */
    error = BCC_CB_Pause(drvConfig, cid, false);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* 12. Exit diagnostic mode. */
    error = BCC_ExitDiagnostics(drvConfig, cid);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* Clear CB OL faults. */
    error = BCC_Reg_Write(drvConfig, cid, BCC_REG_CB_OPEN_FLT_ADDR, 0x0000U, NULL);
    if (error != BCC_STATUS_SUCCESS)
    {
        return error;
    }

    /* Evaluate the diagnostics. */
    if ((results->cbxOpenStatusEven & drvConfig->drvData.cellMap[(uint8_t)cid - 1]) ||
        (results->cbxOpenStatusOdd & drvConfig->drvData.cellMap[(uint8_t)cid - 1]))
    {
        results->error = true;
    }
    else
    {
        results->error = false;
    }

    return BCC_STATUS_SUCCESS;
}
#endif
